package fr.neyb.bernouilli.common.math;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import fr.neyb.bernouilli.common.math.prime.PrimeGenerator1;
import fr.neyb.bernouilli.common.tools.MyArrays;

public class Factorized {

	// ***** TOOLS *****

	public static class PoweredFactor {
		public static Comparator<PoweredFactor> factorComp = new Comparator<Factorized.PoweredFactor>() {
			@Override
			public int compare(PoweredFactor o1, PoweredFactor o2) {
				return (int) (o1.factor - o2.factor);
			}
		};

		public long getFactor() {
			return factor;
		}

		public int getPower() {
			return power;
		}

		public long getValue() {
			return MyMath.pow(factor, power);
		}

		private final long factor;
		private final int power;

		public PoweredFactor(long factor, int power) {
			this.factor = factor;
			this.power = power;
		}

		@Override
		public String toString() {
			return power > 1 ? factor + "^" + power : String.valueOf(factor);
		}
	}

	public static Factorized Merge_KeepBiggestFactors(Factorized f1,
			Factorized f2) {
		List<PoweredFactor> bigPFs = f1.getPoweredFactors();
		List<PoweredFactor> smallPFs = f2.getPoweredFactors();

		if (bigPFs.size() < smallPFs.size()) {
			List<PoweredFactor> tmp = smallPFs;
			smallPFs = bigPFs;
			bigPFs = tmp;
		}

		Collections.sort(bigPFs, PoweredFactor.factorComp);

		for (PoweredFactor pf : smallPFs) {
			int index = Collections.binarySearch(bigPFs, pf,
					PoweredFactor.factorComp);
			if (index < 0) {
				bigPFs.add(pf);
				Collections.sort(bigPFs, PoweredFactor.factorComp);
			} else if (bigPFs.get(index).getPower() < pf.getPower()) {
				bigPFs.set(index, pf);
			}
		}

		return new Factorized(bigPFs);
	}

	public static long getValue(List<PoweredFactor> poweredFactors) {
		long res = 1;
		for (PoweredFactor poweredFactor : poweredFactors) {
			res = MyMath.checkedMult(res, poweredFactor.getValue());
		}
		return res;
	}

	// ***** Implementation *****
	private final List<PoweredFactor> poweredFactors = new ArrayList<>();
	private final long value;

	public Factorized(long l) {
		if (l == 0)
			throw new IllegalArgumentException();

		value = l;

		PrimeGenerator1 pg = PrimeGenerator1.getInstance();

		l = Math.abs(l);
		int primeNum = 0;
		long currPrime = pg.get(primeNum);
		int pow = 0;

		while (l != 1) {
			if (l % currPrime == 0) {
				++pow;
				l /= currPrime;
			} else {
				if (pow > 0)
					poweredFactors.add(new PoweredFactor(currPrime, pow));
				currPrime = pg.get(++primeNum);
				pow = 0;
			}
		}
		poweredFactors.add(new PoweredFactor(currPrime, pow));
		Collections.sort(poweredFactors, PoweredFactor.factorComp);
	}

	public Factorized(Factorized toCopy) {
		value = toCopy.value;
		poweredFactors.addAll(toCopy.poweredFactors);
		Collections.sort(poweredFactors, PoweredFactor.factorComp);
	}

	public Factorized(List<PoweredFactor> poweredFactors) {
		this.poweredFactors.addAll(poweredFactors);
		Collections.sort(poweredFactors, PoweredFactor.factorComp);
		value = getValue(poweredFactors);
	}

	@Override
	public String toString() {
		return MyArrays.toString(poweredFactors.toArray(), "*");
	}

	public long getValue() {
		return value;
	}

	public int getNbDivisors() {
		int res = 1;
		for (PoweredFactor pf : poweredFactors) {
			res *= pf.power + 1;
		}
		return res;
	}

	public Set<Long> getDivisors() {
		Set<Long> tmp = new HashSet<>();
		Set<Long> res = new HashSet<>();
		res.add(1l);
		res.add(value);

		for (PoweredFactor factor : poweredFactors) {
			tmp.clear();
			for (int power = 1; power <= factor.power; ++power) {
				for (Long savedDivisor : res) {
					long toAdd = savedDivisor
							* MyMath.pow(factor.factor, power);
					if (toAdd < value && !res.contains(toAdd)) {
						tmp.add(toAdd);
//						tmp.add(value / toAdd);
					}
				}
			}
			res.addAll(tmp);
		}

		return res;
	}

	public List<PoweredFactor> getPoweredFactors() {
		return new ArrayList<>(poweredFactors);
	}

}
